/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved	*/


/*
 * Copyright (c) 1980 Regents of the University of California.
 * All rights reserved. The Berkeley software License Agreement
 * specifies the terms and conditions for redistribution.
 */

/*
 *  TSET -- set terminal modes
 *
 *	This program does sophisticated terminal initialization.
 *	I recommend that you include it in your .profile or .login
 *	file to initialize whatever terminal you are on.
 *
 *	There are several features:
 *
 *	A special file or sequence (as controlled by the termcap file)
 *	is sent to the terminal.
 *
 *	Mode bits are set on a per-terminal_type basis (much better
 *	than UNIX itself).  This allows special delays, automatic
 *	tabs, etc.
 *
 *	Erase and Kill characters can be set to whatever you want.
 *	Default is to change erase to control-H on a terminal which
 *	can overstrike, and leave it alone on anything else.  Kill
 *	is always left alone unless specifically requested.  These
 *	characters can be represented as "^X" meaning control-X;
 *	X is any character.
 *
 *	Terminals which are dialups or plugboard types can be aliased
 *	to whatever type you may have in your home or office.  Thus,
 *	if you know that when you dial up you will always be on a
 *	TI 733, you can specify that fact to tset.  You can represent
 *	a type as "?type".  This will ask you what type you want it
 *	to be -- if you reply with just a newline, it will default
 *	to the type given.
 *
 *	The current terminal type can be queried.
 *
 *	Usage:
 *		tset [-] [-EC] [-eC] [-kC] [-iC] [-s] [-h] [-u] [-r]
 *			[-m [ident] [test baudrate] :type]
 *			[-Q] [-I] [-S] [type]
 *
 *		In systems with environments, use:
 *			eval `tset -s ...`
 *		Actually, this doesn't work in old csh's.
 *		Instead, use:
 *			tset -s ... > tset.tmp
 *			source tset.tmp
 *			rm tset.tmp
 *		or:
 *			set noglob
 *			set term=(`tset -S ....`)
 *			setenv TERM $term[1]
 *			setenv TERMCAP "$term[2]"
 *			unset term
 *			unset noglob
 *
 *	Positional Parameters:
 *		type -- the terminal type to force.  If this is
 *			specified, initialization is for this
 *			terminal type.
 *
 *	Flags:
 *		- -- report terminal type.  Whatever type is
 *			decided on is reported.  If no other flags
 *			are stated, the only affect is to write
 *			the terminal type on the standard output.
 *		-r -- report to user in addition to other flags.
 *		-EC -- set the erase character to C on all terminals
 *			except those which cannot backspace (e.g.,
 *			a TTY 33).  C defaults to control-H.
 *		-eC -- set the erase character to C on all terminals.
 *			C defaults to control-H.  If not specified,
 *			the erase character is untouched; however, if
 *			not specified and the erase character is NULL
 *			(zero byte), the erase character is set to CERASE.
 *		-kC -- set the kill character to C on all terminals.
 *			Default for C is control-U.  If not specified,
 *			the kill character is untouched; however, if
 *			not specified and the kill character is NULL
 *			(zero byte), the kill character is set to CKILL.
 *		-iC -- set the interrupt character to C on all terminals.
 *			Default for C is control-C.  If not specified, the
 *			interrupt character is untouched; however, if
 *			not specified and the interrupt character is NULL
 *			(zero byte), the interrupt character is set to
 *			control-C.
 *		-qC -- reserved for setable quit character.
 *		-m -- map the system identified type to some user
 *			specified type. The mapping can be baud rate
 *			dependent. This replaces the old -d, -p flags.
 *			(-d type  ->  -m dialup:type)
 *			(-p type  ->  -m plug:type)
 *			Syntax:	-m identifier [test baudrate] :type
 *			where: ``identifier'' is terminal type found in
 *			/etc/ttys for this port, (abscence of an identifier
 *			matches any identifier); ``test'' may be any combination
 *			of  >  =  <  !  @; ``baudrate'' is as with stty(1);
 *			``type'' is the actual terminal type to use if the
 *			mapping condition is met. Multiple maps are scanned
 *			in order and the first match prevails.
 *		-n -- If the new tty driver from UCB is available, this flag
 *			will activate the new options for erase and kill
 *			processing. This will be different for printers
 *			and crt's. For crts, if the baud rate is < 1200 then
 *			erase and kill don't remove characters from the screen.
 *		-h -- don't read htmp file.  Normally the terminal type
 *			is determined by reading the htmp file or the
 *			environment (unless some mapping is specified).
 *			This forces a read of the ttytype file -- useful
 *			when htmp is somehow wrong. (V6 only)
 *		-u -- don't update htmp.  It seemed like this should
 *			be put in.  Note that htmp is never actually
 *			written if there are no changes, so don't bother
 *			bother using this for efficiency reasons alone.
 *		-s -- output setenv commands for TERM.  This can be
 *			used with
 *				`tset -s ...`
 *			and is to be prefered to:
 *				setenv TERM `tset - ...`
 *			because -s sets the TERMCAP variable also.
 *		-S -- Similar to -s but outputs 2 strings suitable for
 *			use in csh .login files as follows:
 *				set noglob
 *				set term=(`tset -S .....`)
 *				setenv TERM $term[1]
 *				setenv TERMCAP "$term[2]"
 *				unset term
 *				unset noglob
 *		-Q -- be quiet.  don't output 'Erase set to' etc.
 *		-I -- don't do terminal initialization (is & if
 *			strings).
 *		-v -- On virtual terminal systems, don't set up a
 *			virtual terminal.  Otherwise tset will tell
 *			the operating system what kind of terminal you
 *			are on (if it is a known terminal) and fix up
 *			the output of -s to use virtual terminal sequences.
 *
 *	Files:
 *		/etc/ttys
 *			contains a terminal id -> terminal type
 *			mapping; used when any user mapping is specified,
 *			or the environment doesn't have TERM set.
 *		/etc/termcap
 *			a terminal_type -> terminal_capabilities
 *			mapping.
 *
 *	Return Codes:
 *		-1 -- couldn't open termcap.
 *		1 -- bad terminal type, or standard output not tty.
 *		0 -- ok.
 *
 *	Defined Constants:
 *		DIALUP -- the type code for a dialup port.
 *		PLUGBOARD -- the type code for a plugboard port.
 *		ARPANET -- the type code for an arpanet port.
 *		BACKSPACE -- control-H, the default for -e.
 *		CNTL('U') -- control-U, the default for -k.
 *		OLDERASE -- the ancient default erase character.
 *		FILEDES -- the file descriptor to do the operation
 *			on, nominally 1 or 2.
 *		STDOUT -- the standard output file descriptor.
 *		UIDMASK -- the bit pattern to mask with the getuid()
 *			call to get just the user id.
 *		GTTYN -- defines file containing generalized ttynames
 *			and compiles code to look there.
 *
 *	Requires:
 *		Routines to handle htmp, ttys, and termcap.
 *
 *	Compilation Flags:
 *		OLDFLAGS -- must be defined to compile code for any of
 *			the -d, -p, or -a flags.
 *		OLDDIALUP -- accept the -d flag.
 *		OLDPLUGBOARD -- accept the -p flag.
 *		OLDARPANET -- accept the -a flag.
 *		V6 -- if clear, use environments, not htmp.
 *			also use TIOCSETN rather than stty to avoid flushing
 *		GTTYN -- if set, compiles code to look at /etc/ttys.
 *
 *	Trace Flags:
 *		none
 *
 *	Diagnostics:
 *		Bad flag
 *			An incorrect option was specified.
 *		Too few args
 *			more command line arguments are required.
 *		Unexpected arg
 *			wrong type of argument was encountered.
 *		Cannot open ...
 *			The specified file could not be openned.
 *		Type ... unknown
 *			An unknown terminal type was specified.
 *		Cannot update htmp
 *			Cannot update htmp file when the standard
 *			output is not a terminal.
 *		Erase set to ...
 *			Telling that the erase character has been
 *			set to the specified character.
 *		Kill set to ...
 *			Ditto for kill
 *		Erase is ...    Kill is ...
 *			Tells that the erase/kill characters were
 *			wierd before, but they are being left as-is.
 *		Not a terminal
 *			Set if FILEDES is not a terminal.
 *
 *	Compilation Instructions:
 *		cc -n -O tset.c -ltermlib
 *		mv a.out tset
 *		chown bin tset
 *		chmod 4755 tset
 *
 *		where 'bin' should be whoever owns the 'htmp' file.
 *		If 'htmp' is 666, then tset need not be setuid.
 *
 *		For version 6 the compile command should be:
 *		cc -n -O -I/usr/include/retrofit tset.c -ltermlib -lretro -lS
 *
 *
 *	History:
 *		1/81 -- Added alias checking for mapping identifiers.
 *		7/80 -- '-S' added. '-m' mapping added. TERMCAP string
 *			cleaned up.
 *		3/80 -- Changed to use tputs.  Prc & flush added.
 *		10/79 -- '-s' option extended to handle TERMCAP
 *			variable, set noglob, quote the entry,
 *			and know about the Bourne shell.  Terminal
 *			initialization moved to before any information
 *			output so screen clears would not screw you.
 *			'-Q' option added.
 *		8/79 -- '-' option alone changed to only output
 *			type.  '-s' option added.  'VERSION7'
 *			changed to 'V6' for compatibility.
 *		12/78 -- modified for eventual migration to VAX/UNIX,
 *			so the '-' option is changed to output only
 *			the terminal type to STDOUT instead of
 *			FILEDES.
 *		9/78 -- '-' and '-p' options added (now fully
 *			compatible with ttytype!), and spaces are
 *			permitted between the -d and the type.
 *		8/78 -- The sense of -h and -u were reversed, and the
 *			-f flag is dropped -- same effect is available
 *			by just stating the terminal type.
 *		10/77 -- Written.
 */


#define	index strchr
#define	rindex strrchr
#define	curerase modes.c_cc[VERASE]
#define	curkill modes.c_cc[VKILL]
#define	curintr modes.c_cc[VINTR]
#define	olderase oldmodes.c_cc[VERASE]
#define	oldkill oldmodes.c_cc[VKILL]
#define	oldintr oldmodes.c_cc[VINTR]

#include	<stdio.h>
#include	<stdlib.h>
#include	<termio.h>
#include	<signal.h>


#define	YES		1
#define	NO		0
#undef CNTL
#define	CNTL(c)		((c)&037)
#define	BACKSPACE	(CNTL('H'))
#define	isdigit(c)	(c >= '0' && c <= '9')
#define	isalnum(c)	(c > ' ' && (index("<@=>!:|\177", c) == NULL))
#define	OLDERASE	'#'

/* default special characters */
#ifndef CERASE
#define	CERASE	'\177'
#endif
#ifndef CKILL
#define	CKILL	CNTL('U')
#endif
#ifndef CINTR
#define	CINTR	CNTL('C')
#endif
#ifndef CDSUSP
#define	CQUIT	034		/* FS, ^\ */
#define	CSTART	CNTL('Q')
#define	CSTOP	CNTL('S')
#define	CEOF	CNTL('D')
#define	CEOT	CEOF
#define	CBRK	0377
#define	CSUSP	CNTL('Z')
#define	CDSUSP	CNTL('Y')
#define	CRPRNT	CNTL('R')
#define	CFLUSH	CNTL('O')
#define	CWERASE	CNTL('W')
#define	CLNEXT	CNTL('V')
#endif

#define	FILEDES		2	/* do gtty/stty on this descriptor */
#define	STDOUT		1	/* output of -s/-S to this descriptor */

#define	UIDMASK		-1

#define	USAGE	"usage: tset [-] [-rsIQS] [-eC] [-kC] "	\
		"[-iC] [-m [ident][test speed]:type] [type]\n"

#define	OLDFLAGS
#define	DIALUP		"dialup"
#define	OLDDIALUP	"sd"
#define	PLUGBOARD	"plugboard"
#define	OLDPLUGBOARD	"sp"

#define	DEFTYPE		"unknown"

/*
 * Baud Rate Conditionals
 */
#define	ANY		0
#define	GT		1
#define	EQ		2
#define	LT		4
#define	GE		(GT|EQ)
#define	LE		(LT|EQ)
#define	NE		(GT|LT)
#define	ALL		(GT|EQ|LT)



#define	NMAP		10

struct	map {
	char *Ident;
	char Test;
	char Speed;
	char *Type;
} map[NMAP];

struct map *Map = map;

struct {
	char	*string;
	int	speed;
	int	baudrate;
} speeds[] = {
	"0",		B0,		0,
	"50",		B50,		50,
	"75",		B75,		75,
	"110",		B110,		110,
	"134",		B134,		134,
	"134.5",	B134,		134,
	"150",		B150,		150,
	"200",		B200,		200,
	"300",		B300,		300,
	"600",		B600,		600,
	"1200",		B1200,		1200,
	"1800",		B1800,		1800,
	"2400",		B2400,		2400,
	"4800",		B4800,		4800,
	"9600",		B9600,		9600,
	"19200",	EXTA,		19200,
	"exta",		EXTA,		19200,
	"extb",		EXTB,		38400,
	"57600",	B57600,		57600,
	"76800",	B76800,		76800,
	"115200",	B115200,	115200,
	"153600",	B153600,	153600,
	"230400",	B230400,	230400,
	"307200",	B307200,	307200,
	"460800",	B460800,	460800,
	"921600",	B921600,	921600,
	"1000000",	B1000000,	1000000,
	"1152000",	B1152000,	1152000,
	"1500000",	B1500000,	1500000,
	"2000000",	B2000000,	2000000,
	"2500000",	B2500000,	2500000,
	"3000000",	B3000000,	3000000,
	"3500000",	B3500000,	3500000,
	"4000000",	B4000000,	4000000,
	0,
};

signed char Erase_char;		/* new erase character */
char	Kill_char;		/* new kill character */
char	Intr_char;		/* new interrupt character */
char	Specialerase;	/* set => Erase_char only on terminals with backspace */

char	*TtyType;		/* type of terminal */
char	*DefType;		/* default type if none other computed */
char	*NewType;		/* mapping identifier based on old flags */
int	Mapped;			/* mapping has been specified */
int	Dash_u;			/* don't update htmp */
int	Dash_h;			/* don't read htmp */
int	DoSetenv;		/* output setenv commands */
int	BeQuiet;		/* be quiet */
int	NoInit;			/* don't output initialization string */
int	IsReset;		/* invoked as reset */
int	Report;			/* report current type */
int	Ureport;		/* report to user */
int	RepOnly;		/* report only */
int	CmndLine;		/* output full command lines (-s option) */
int	Ask;			/* ask user for termtype */
int	DoVirtTerm = YES;	/* Set up a virtual terminal */
int	PadBaud;		/* Min rate of padding needed */

#define	CAPBUFSIZ	1024
char	Capbuf[CAPBUFSIZ];	/* line from /etc/termcap for this TtyType */
char	*Ttycap;		/* termcap line from termcap or environ */

char	Aliasbuf[128];
char	*Alias[16];

extern char *strcpy();
extern char *index();

struct delay
{
	int	d_delay;
	int	d_bits;
};

#include	"tset.delays.h"

struct termio	mode;
struct termio	oldmode;
struct termios	modes;
struct termios	oldmodes;
int		istermios;

void reportek(char *, char, char, char);
void setdelay(char *, struct delay [], tcflag_t, tcflag_t *);
void prs(char *);
void prc(char);
void flush(void);
void cat(char *);
void bmove(char *, char *, int);
void makealias(char *);
void wrtermcap(char *);
void fatal(char *, char *);
char reset();			/* Routine for checking&resetting chars */

int
main(int argc, char *argv[])
{
	char		buf[CAPBUFSIZ];
	char		termbuf[32];
	auto char	*bufp;
	char		*p;
	char		*command;
	int		i;
	int		Break;
	int		Not;
	char		*nextarg();
	char		*mapped();
	extern char	*rindex();
	struct winsize	win;
	extern char	*getenv();
	extern char	*tgetstr();
	char		bs_char;
	int		csh;
	int		settle = NO;
	void		setmode();
	extern char	PC;
	extern short	ospeed;

	if ((istermios = ioctl(FILEDES, TCGETS, (char *)&modes)) < 0) {
		if (ioctl(FILEDES, TCGETA, (char *)&mode) < 0) {
			prs("Not a terminal\n");
			exit(1);
		}
		bmove((char *)&mode, (char *)&oldmode, sizeof (mode));
		modes.c_lflag = oldmodes.c_lflag = mode.c_lflag;
		modes.c_oflag = oldmodes.c_oflag = mode.c_oflag;
		modes.c_iflag = oldmodes.c_iflag = mode.c_iflag;
		modes.c_cflag = oldmodes.c_cflag = mode.c_cflag;
		for (i = 0; i < NCC; i++)
			modes.c_cc[i] = oldmodes.c_cc[i] = mode.c_cc[i];
	} else
		bmove((char *)&modes, (char *)&oldmodes, sizeof (modes));
	ospeed = cfgetospeed(&modes);
	(void) signal(SIGINT, setmode);
	(void) signal(SIGQUIT, setmode);
	(void) signal(SIGTERM, setmode);

	if (command = rindex(argv[0], '/'))
		command++;
	else
		command = argv[0];
	if (sequal(command, "reset")) {
		/*
		 * Reset the teletype mode bits to a sensible state.
		 * Copied from the program by Kurt Shoens & Mark Horton.
		 * Very useful after crapping out in raw.
		 */
		if ((istermios = ioctl(FILEDES, TCGETS, (char *)&modes)) < 0) {
			(void) ioctl(FILEDES, TCGETA, (char *)&mode);
			modes.c_lflag = mode.c_lflag;
			modes.c_oflag = mode.c_oflag;
			modes.c_iflag = mode.c_iflag;
			modes.c_cflag = mode.c_cflag;
			for (i = 0; i < NCC; i++)
				modes.c_cc[i] = mode.c_cc[i];
		}
		curerase = reset(curerase, CERASE);
		curkill = reset(curkill, CKILL);
		curintr = reset(curintr, CINTR);
		modes.c_cc[VQUIT] = reset(modes.c_cc[VQUIT], CQUIT);
		modes.c_cc[VEOF] = reset(modes.c_cc[VEOF], CEOF);

		modes.c_iflag |= (BRKINT|ISTRIP|ICRNL|IXON);
		modes.c_iflag &= ~(IGNBRK|PARMRK|INPCK|INLCR|IGNCR|IUCLC|IXOFF);
		modes.c_oflag |= (OPOST|ONLCR);
		modes.c_oflag &= ~(OLCUC|OCRNL|ONOCR|ONLRET|OFILL|OFDEL|
		    NLDLY|CRDLY|TABDLY|BSDLY|VTDLY|FFDLY);
		modes.c_cflag |= (CS7|CREAD);
		modes.c_cflag &= ~(PARODD|CLOCAL);
		modes.c_lflag |= (ISIG|ICANON|ECHO|ECHOK);
		modes.c_lflag &= ~(XCASE|ECHONL|NOFLSH);
		if (istermios < 0) {
			mode.c_lflag = modes.c_lflag;
			mode.c_oflag = modes.c_oflag;
			mode.c_iflag = modes.c_iflag;
			mode.c_cflag = modes.c_cflag;
			for (i = 0; i < NCC; i++)
				mode.c_cc[i] = modes.c_cc[i];
			(void) ioctl(FILEDES, TCSETAW, (char *)&mode);
		} else
			(void) ioctl(FILEDES, TCSETSW, (char *)&modes);
		Dash_u = YES;
		BeQuiet = YES;
		IsReset = YES;
	} else if (argc == 2 && sequal(argv[1], "-")) {
		RepOnly = YES;
		Dash_u = YES;
	}
	argc--;

	/* scan argument list and collect flags */
	while (--argc >= 0) {
		p = *++argv;
		if (*p == '-') {
			if (*++p == '\0')
				Report = YES; /* report current terminal type */
			else
				while (*p)
					switch (*p++) {

			case 'r':	/* report to user */
				Ureport = YES;
				continue;

			case 'E':
				/* special erase: operate on all but TTY33 */
				Specialerase = YES;
				/* explicit fall-through to -e case */
				/* FALLTHROUGH */

			case 'e':	/* erase character */
				if (*p == '\0')
					Erase_char = -1;
				else {
					if (*p == '^' && p[1] != '\0')
						if (*++p == '?')
							Erase_char = '\177';
						else
							Erase_char = CNTL(*p);
					else
						Erase_char = *p;
					p++;
				}
				continue;

			case 'i':	/* interrupt character */
				if (*p == '\0')
					Intr_char = CNTL('C');
				else {
					if (*p == '^' && p[1] != '\0')
						if (*++p == '?')
							Intr_char = '\177';
						else
							Intr_char = CNTL(*p);
					else
						Intr_char = *p;
					p++;
				}
				continue;

			case 'k':	/* kill character */
				if (*p == '\0')
					Kill_char = CNTL('U');
				else {
					if (*p == '^' && p[1] != '\0')
						if (*++p == '?')
							Kill_char = '\177';
						else
							Kill_char = CNTL(*p);
					else
						Kill_char = *p;
					p++;
				}
				continue;

#ifdef OLDFLAGS
#ifdef	OLDDIALUP
			case 'd':	/* dialup type */
				NewType = DIALUP;
				goto mapold;
#endif

#ifdef OLDPLUGBOARD
			case 'p':	/* plugboard type */
				NewType = PLUGBOARD;
				goto mapold;
#endif

#ifdef OLDARPANET
			case 'a':	/* arpanet type */
				Newtype = ARPANET;
				goto mapold;
#endif

mapold:				Map->Ident = NewType;
				Map->Test = ALL;
				if (*p == '\0') {
					p = nextarg(argc--, argv++);
				}
				Map->Type = p;
				Map++;
				Mapped = YES;
				p = "";
				continue;
#endif

			case 'm':	/* map identifier to type */
				/*
				 * This code is very loose. Almost no
				 * syntax checking is done!! However,
				 * illegal syntax will only produce
				 * weird results.
				 */
				if (*p == '\0') {
					p = nextarg(argc--, argv++);
				}
				if (isalnum(*p)) {
					Map->Ident = p;	/* identifier */
					while (isalnum(*p)) p++;
				}
				else
					Map->Ident = "";
				Break = NO;
				Not = NO;
				while (!Break)
					switch (*p) {
					case '\0':
						p = nextarg(argc--, argv++);
						continue;

					case ':':	/* mapped type */
						*p++ = '\0';
						Break = YES;
						continue;

					case '>':	/* conditional */
						Map->Test |= GT;
						*p++ = '\0';
						continue;

					case '<':	/* conditional */
						Map->Test |= LT;
						*p++ = '\0';
						continue;

					case '=':	/* conditional */
					case '@':
						Map->Test |= EQ;
						*p++ = '\0';
						continue;

					case '!':	/* invert conditions */
						Not = ~Not;
						*p++ = '\0';
						continue;

					case 'B':	/* Baud rate */
						p++;
						/* intentional fallthru */
					default:
						if (isdigit(*p) || *p == 'e') {
							Map->Speed =
							    baudrate(p);
							while (isalnum(*p) ||
							    *p == '.')
								p++;
						} else
							Break = YES;
						continue;
				}
				if (Not) {	/* invert sense of test */
					Map->Test = (~(Map->Test))&ALL;
				}
				if (*p == '\0') {
					p = nextarg(argc--, argv++);
				}
				Map->Type = p;
				p = "";
				Map++;
				Mapped = YES;
				continue;

			case 'h':	/* don't get type from htmp or env */
				Dash_h = YES;
				continue;

			case 'u':	/* don't update htmp */
				Dash_u = YES;
				continue;

			case 's':	/* output setenv commands */
				DoSetenv = YES;
				CmndLine = YES;
				continue;

			case 'S':	/* output setenv strings */
				DoSetenv = YES;
				CmndLine = NO;
				continue;

			case 'Q':	/* be quiet */
				BeQuiet = YES;
				continue;

			case 'I':	/* no initialization */
				NoInit = YES;
				continue;

			case 'A':	/* Ask user */
				Ask = YES;
				continue;

			case 'v':	/* no virtual terminal */
				DoVirtTerm = NO;
				continue;

			default:
				*p-- = '\0';
				fatal("Bad flag -", p);
			}
		} else {
			/* terminal type */
			DefType = p;
		}
	}

	if (DefType) {
		if (Mapped) {
			Map->Ident = "";	/* means "map any type" */
			Map->Test = ALL;	/* at all baud rates */
			Map->Type = DefType;	/* to the default type */
		} else
			TtyType = DefType;
	}

	/*
	 * Get rid of $TERMCAP, if it's there, so we get a real
	 * entry from /etc/termcap.  This prevents us from being
	 * fooled by out of date stuff in the environment, and
	 * makes tabs work right on CB/Unix.
	 */
	bufp = getenv("TERMCAP");
	if (bufp && *bufp != '/')
		(void) strcpy(bufp-8, "NOTHING"); /* overwrite only "TERMCAP" */
	/* get current idea of terminal type from environment */
	if (!Dash_h && TtyType == NULL)
		TtyType = getenv("TERM");

	/* If still undefined, use DEFTYPE */
	if (TtyType == NULL) {
		TtyType = DEFTYPE;
	}

	/* check for dialup or other mapping */
	if (Mapped) {
		if (!(Alias[0] && isalias(TtyType)))
			if (tgetent(Capbuf, TtyType) > 0)
				makealias(Capbuf);
		TtyType = mapped(TtyType);
	}

	/* TtyType now contains a pointer to the type of the terminal */
	/* If the first character is '?', ask the user */
	if (TtyType[0] == '?') {
		Ask = YES;
		TtyType++;
		if (TtyType[0] == '\0')
			TtyType = DEFTYPE;
	}
	if (Ask) {
ask:
		prs("TERM = (");
		prs(TtyType);
		prs(") ");
		flush();

		/* read the terminal.  If not empty, set type */
		i = read(2, termbuf, sizeof (termbuf) - 1);
		if (i > 0) {
			if (termbuf[i - 1] == '\n')
				i--;
			termbuf[i] = '\0';
			if (termbuf[0] != '\0')
				TtyType = termbuf;
		}
	}

	/* get terminal capabilities */
	if (!(Alias[0] && isalias(TtyType))) {
		switch (tgetent(Capbuf, TtyType)) {
		case -1:
			prs("Cannot find termcap\n");
			flush();
			exit(-1);

		case 0:
			prs("Type ");
			prs(TtyType);
			prs(" unknown\n");
			flush();
			if (DoSetenv) {
				TtyType = DEFTYPE;
				Alias[0] = '\0';
				goto ask;
			} else
				exit(1);
		}
	}
	Ttycap = Capbuf;

	if (!RepOnly) {
		/* determine erase and kill characters */
		if (Specialerase && !tgetflag("bs"))
			Erase_char = 0;
		bufp = buf;
		p = tgetstr("kb", &bufp);
		if (p == NULL || p[1] != '\0')
			p = tgetstr("bc", &bufp);
		if (p != NULL && p[1] == '\0')
			bs_char = p[0];
		else if (tgetflag("bs"))
			bs_char = BACKSPACE;
		else
			bs_char = 0;
		/*
		 * The next statement can't be fixed, because now users
		 * depend on keeping their erase character as DEL if the
		 * system set it there.  People who want backspace have
		 * to say tset -e.
		 */
		if (Erase_char == 0 && !tgetflag("os") &&
		    curerase == OLDERASE) {
			if (tgetflag("bs") || bs_char != 0)
				Erase_char = -1;
		}
		if (Erase_char < 0)
			Erase_char = (bs_char != 0) ? bs_char : BACKSPACE;

		if (curerase == 0)
			curerase = CERASE;
		if (Erase_char != 0)
			curerase = Erase_char;

		if (curintr == 0)
			curintr = CINTR;
		if (Intr_char != 0)
			curintr = Intr_char;

		if (curkill == 0)
			curkill = CKILL;
		if (Kill_char != 0)
			curkill = Kill_char;

		/* set modes */
		PadBaud = tgetnum("pb");	/* OK if fails */
		for (i = 0; speeds[i].string; i++)
			if (speeds[i].baudrate == PadBaud) {
				PadBaud = speeds[i].speed;
				break;
			}
		setdelay("dC", CRdelay, CRbits, &modes.c_oflag);
		setdelay("dN", NLdelay, NLbits, &modes.c_oflag);
		setdelay("dB", BSdelay, BSbits, &modes.c_oflag);
		setdelay("dF", FFdelay, FFbits, &modes.c_oflag);
		setdelay("dT", TBdelay, TBbits, &modes.c_oflag);
		setdelay("dV", VTdelay, VTbits, &modes.c_oflag);

		if (tgetflag("UC") || (command[0] & 0140) == 0100) {
			modes.c_iflag |= IUCLC;
			modes.c_oflag |= OLCUC;
			modes.c_cflag |= XCASE;
		} else if (tgetflag("LC")) {
			modes.c_iflag &= ~IUCLC;
			modes.c_oflag &= ~OLCUC;
			modes.c_cflag &= ~XCASE;
		}
		modes.c_iflag &= ~(PARMRK|INPCK);
		modes.c_lflag |= ICANON;
		if (tgetflag("EP")) {
			modes.c_iflag |= INPCK;
			modes.c_cflag |= PARENB;
			modes.c_cflag &= ~PARODD;
		}
		if (tgetflag("OP")) {
			modes.c_iflag |= INPCK;
			modes.c_cflag |= PARENB;
			modes.c_cflag |= PARODD;
		}

		modes.c_oflag |= ONLCR;
		modes.c_iflag |= ICRNL;
		modes.c_lflag |= ECHO;
		modes.c_oflag |= TAB3;
		if (tgetflag("NL")) {	/* new line, not line feed */
			modes.c_oflag &= ~ONLCR;
			modes.c_iflag &= ~ICRNL;
		}
		if (tgetflag("HD"))	/* half duplex */
			modes.c_lflag &= ~ECHO;
		if (tgetflag("pt"))	/* print tabs */
			modes.c_oflag &= ~TAB3;

		modes.c_lflag |= (ECHOE|ECHOK);
		if (tgetflag("hc")) {	/* set printer modes */
			modes.c_lflag &= ~ECHOE;
		}

		/* get pad character */
		bufp = buf;
		if (tgetstr("pc", &bufp) != 0)
			PC = buf[0];

		/* output startup string */
		if (!NoInit) {
			if (oldmodes.c_oflag&(TAB3|ONLCR|OCRNL|ONLRET)) {
				oldmodes.c_oflag &= (TAB3|ONLCR|OCRNL|ONLRET);
				setmode(-1);
			}
			if (settabs()) {
				settle = YES;
				flush();
			}
			bufp = buf;
			if (IsReset && tgetstr("rs", &bufp) != 0 ||
			    tgetstr("is", &bufp) != 0) {
				tputs(buf, 0, prc);
				settle = YES;
				flush();
			}
			bufp = buf;
			if (IsReset && tgetstr("rf", &bufp) != 0 ||
			    tgetstr("if", &bufp) != 0) {
				cat(buf);
				settle = YES;
			}
			if (settle) {
				prc('\r');
				if (IsReset)
					prc('\n');  /* newline too */
				flush();
				sleep(1);	/* let terminal settle down */
			}
		}

		setmode(0);	/* set new modes, if they've changed */

		/* set up environment for the shell we are using */
		/* (this code is rather heuristic, checking for $SHELL */
		/* ending in the 3 characters "csh") */
		csh = NO;
		if (DoSetenv) {
			char *sh;

			if ((sh = getenv("SHELL")) && (i = strlen(sh)) >= 3) {
				if ((csh = sequal(&sh[i-3], "csh")) && CmndLine)
					(void) write(STDOUT,
					    "set noglob;\n", 12);
			}
			if (!csh) {	/* running Bourne shell */
				(void) write(STDOUT,
				    "export TERMCAP TERM;\n", 21);
			}
		}
	}

	/* report type if appropriate */
	if (DoSetenv || Report || Ureport) {
		/* if type is the short name, find first alias (if any) */
		makealias(Ttycap);
		if (sequal(TtyType, Alias[0]) && Alias[1]) {
			TtyType = Alias[1];
		}

		if (DoSetenv) {
			if (csh) {
				if (CmndLine)
					(void) write(STDOUT,
					    "setenv TERM ", 12);
				(void) write(STDOUT, TtyType, strlen(TtyType));
				(void) write(STDOUT, " ", 1);
				if (CmndLine)
					(void) write(STDOUT, ";\n", 2);
			} else {
				(void) write(STDOUT, "TERM=", 5);
				(void) write(STDOUT, TtyType, strlen(TtyType));
				(void) write(STDOUT, ";\n", 2);
			}
		} else if (Report) {
			(void) write(STDOUT, TtyType, strlen(TtyType));
			(void) write(STDOUT, "\n", 1);
		}
		if (Ureport) {
			prs("Terminal type is ");
			prs(TtyType);
			prs("\n");
			flush();
		}

		if (DoSetenv) {
			if (csh) {
				if (CmndLine)
					(void) write(STDOUT,
					    "setenv TERMCAP '", 16);
			} else
				(void) write(STDOUT, "TERMCAP='", 9);
			wrtermcap(Ttycap);
			if (csh) {
				if (CmndLine) {
					(void) write(STDOUT, "';\n", 3);
					(void) write(STDOUT,
					    "unset noglob;\n", 14);
				}
			} else
				(void) write(STDOUT, "';\n", 3);
		}
	}

	if (RepOnly)
		exit(0);

	/* tell about changing erase, kill and interrupt characters */
	reportek("Erase", curerase, olderase, CERASE);
	reportek("Kill", curkill, oldkill, CKILL);
	reportek("Interrupt", curintr, oldintr, CINTR);

	return (0);
}

/*
 * Set the hardware tabs on the terminal, using the ct (clear all tabs),
 * st (set one tab) and ch (horizontal cursor addressing) capabilities.
 * This is done before if and is, so they can patch in case we blow this.
 */
int
settabs(void)
{
	char caps[100];
	char *capsp = caps;
	char *clear_tabs, *set_tab, *set_column, *set_pos;
	char *tg_out, *tgoto();
	int c;
	extern char *tgetstr();
	int lines, columns;

	clear_tabs = tgetstr("ct", &capsp);
	set_tab = tgetstr("st", &capsp);
	set_column = tgetstr("ch", &capsp);
	if (set_column == 0)
		set_pos = tgetstr("cm", &capsp);

	if (clear_tabs && set_tab) {
		prc('\r');	/* force to be at left margin */
		tputs(clear_tabs, 0, prc);
	}
	if (set_tab) {
		columns = tgetnum("co");
		lines = tgetnum("li");
		for (c = 0; c < columns; c += 8) {
			/* get to that column. */
			tg_out = "OOPS";	/* also returned by tgoto */
			if (set_column)
				tg_out = tgoto(set_column, 0, c);
			if (*tg_out == 'O' && set_pos)
				tg_out = tgoto(set_pos, c, lines-1);
			if (*tg_out != 'O')
				tputs(tg_out, 1, prc);
			else if (c != 0) {
				prc(' '); prc(' '); prc(' '); prc(' ');
				prc(' '); prc(' '); prc(' '); prc(' ');
			}
			/* set the tab */
			tputs(set_tab, 0, prc);
		}
		prc('\r');
		return (1);
	}
	return (0);
}

/*
 * flag serves several purposes:
 *	if called as the result of a signal, flag will be > 0.
 *	if called from terminal init, flag == -1 means reset "oldmode".
 *	called with flag == 0 at end of normal mode processing.
 */
void
setmode(int flag)
{
	struct termio *ttymode;
	struct termios *ttymodes;
	int i;

	ttymode = (struct termio *)0;
	ttymodes = (struct termios *)0;

	if (flag < 0) { /* unconditionally reset oldmode (called from init) */
		if (istermios < 0) {
			oldmode.c_lflag = oldmodes.c_lflag;
			oldmode.c_oflag = oldmodes.c_oflag;
			oldmode.c_iflag = oldmodes.c_iflag;
			oldmode.c_cflag = oldmodes.c_cflag;
			for (i = 0; i < NCC; i++)
				oldmode.c_cc[i] = oldmodes.c_cc[i];
			ttymode = &oldmode;
		} else
			ttymodes = &oldmodes;
	} else {
		if (istermios < 0) {
			oldmode.c_lflag = oldmodes.c_lflag;
			oldmode.c_oflag = oldmodes.c_oflag;
			oldmode.c_iflag = oldmodes.c_iflag;
			oldmode.c_cflag = oldmodes.c_cflag;
			for (i = 0; i < NCC; i++)
				oldmode.c_cc[i] = oldmodes.c_cc[i];
			mode.c_lflag = modes.c_lflag;
			mode.c_oflag = modes.c_oflag;
			mode.c_iflag = modes.c_iflag;
			mode.c_cflag = modes.c_cflag;
			for (i = 0; i < NCC; i++)
				mode.c_cc[i] = modes.c_cc[i];
			if (!bequal((char *)&mode, (char *)&oldmode,
			    sizeof (mode)))
				ttymode = &mode;
		} else if (!bequal((char *)&modes, (char *)&oldmodes,
		    sizeof (modes)))
			ttymodes = &modes;
	}

	if (ttymode) {
		(void) ioctl(FILEDES, TCSETAW, (char *)ttymode);
	} else if (ttymodes) {
		(void) ioctl(FILEDES, TCSETSW, (char *)ttymodes);
	}
	if (flag > 0)	/* trapped signal */
		exit(1);
}

void
reportek(char *name, char new, char old, char def)
{
	char	o;
	char	n;
	char	*p;
	char		buf[32];
	char		*bufp;
	extern char *tgetstr();

	if (BeQuiet)
		return;
	o = old;
	n = new;

	if (o == n && n == def)
		return;
	prs(name);
	if (o == n)
		prs(" is ");
	else
		prs(" set to ");
	bufp = buf;
	if (tgetstr("kb", &bufp) > (char *)0 && n == buf[0] && buf[1] == '\0')
		prs("Backspace\n");
	else if (n == 0177)
		prs("Delete\n");
	else {
		if (n < 040) {
			prs("Ctrl-");
			n ^= 0100;
		}
		p = "x\n";
		p[0] = n;
		prs(p);
	}
	flush();
}



void
setdelay(char *cap, struct delay dtab[], tcflag_t bits, tcflag_t *flags)
{
	int		i;
	struct delay	*p;
	extern short	ospeed;

	/* see if this capability exists at all */
	i = tgetnum(cap);
	if (i < 0)
		i = 0;
	/* No padding at speeds below PadBaud */
	if (PadBaud > ospeed)
		i = 0;

	/* clear out the bits, replace with new ones */
	*flags &= ~bits;

	/* scan dtab for first entry with adequate delay */
	for (p = dtab; p->d_delay >= 0; p++) {
		if (p->d_delay >= i) {
			p++;
			break;
		}
	}

	/* use last entry if none will do */
	*flags |= (tcflag_t)((--p)->d_bits);
}

void
prs(char *s)
{
	while (*s != '\0')
		prc(*s++);
}


char	OutBuf[256];
int	OutPtr;

void
prc(char c)
{
	OutBuf[OutPtr++] = c;
	if (OutPtr >= sizeof (OutBuf))
		flush();
}

void
flush(void)
{
	if (OutPtr > 0)
		(void) write(2, OutBuf, OutPtr);
	OutPtr = 0;
}

void
cat(char *file)
{
	int	fd;
	int	i;
	char		buf[BUFSIZ];

	fd = open(file, 0);
	if (fd < 0) {
		prs("Cannot open ");
		prs(file);
		prs("\n");
		flush();
		return;
	}

	while ((i = read(fd, buf, BUFSIZ)) > 0)
		(void) write(FILEDES, buf, i);

	(void) close(fd);
}


void
bmove(char *from, char *to, int length)
{
	char	*p, *q;
	int	i;

	i = length;
	p = from;
	q = to;

	while (i-- > 0)
		*q++ = *p++;
}


int
bequal(char *a, char *b, int len)	/* must be same thru len chars */
{
	char	*p, *q;
	int	i;

	i = len;
	p = a;
	q = b;

	while ((*p == *q) && --i > 0) {
		p++; q++;
	}
	return ((*p == *q) && i >= 0);
}

int
sequal(char *a, char *b)	/* must be same thru NULL */
{
	char *p = a, *q = b;

	while (*p && *q && (*p == *q)) {
		p++; q++;
	}
	return (*p == *q);
}

void
makealias(char *buf)
{
	int i;
	char *a;
	char *b;

	Alias[0] = a = Aliasbuf;
	b = buf;
	i = 1;
	while (*b && *b != ':') {
		if (*b == '|') {
			*a++ = '\0';
			Alias[i++] = a;
			b++;
		} else
			*a++ = *b++;
	}
	*a = '\0';
	Alias[i] = NULL;
#ifdef	DEB
	for (i = 0; Alias[i]; printf("A:%s\n", Alias[i++]))
		;
#endif
}

int
isalias(char *ident)	/* is ident same as one of the aliases? */
{
	char **a = Alias;

	if (*a)
		while (*a)
			if (sequal(ident, *a))
				return (YES);
			else
				a++;
	return (NO);
}


/*
 * routine to output the string for the environment TERMCAP variable
 */
#define	WHITE(c)	(c == ' ' || c == '\t')
char delcap[128][2];
int ncap = 0;

void
wrtermcap(char *bp)
{
	char buf[CAPBUFSIZ];
	char *p = buf;
	char *tp;
	char *putbuf();
	int space, empty;

	/* discard names with blanks */
/* May not be desireable ? */
	while (*bp && *bp != ':') {
		if (*bp == '|') {
			tp = bp+1;
			space = NO;
			while (*tp && *tp != '|' && *tp != ':') {
				space = (space || WHITE(*tp));
				tp++;
			}
			if (space) {
				bp = tp;
				continue;
			}
		}
		*p++ = *bp++;
	}

	while (*bp) {
		switch (*bp) {
		case ':':	/* discard empty, cancelled  or dupl fields */
			tp = bp + 1;
			empty = YES;
			while (*tp && *tp != ':') {
				empty = (empty && WHITE(*tp));
				tp++;
			}
			if (empty || cancelled(bp+1)) {
				bp = tp;
				continue;
			}
			break;

		case ' ':	/* no spaces in output */
			p = putbuf(p, "\\040");
			bp++;
			continue;

		case '!':	/* the shell thinks this is history */
			p = putbuf(p, "\\041");
			bp++;
			continue;

		case ',':	/* the shell thinks this is history */
			p = putbuf(p, "\\054");
			bp++;
			continue;

		case '"':	/* no quotes in output */
			p = putbuf(p, "\\042");
			bp++;
			continue;

		case '\'':	/* no quotes in output */
			p = putbuf(p, "\\047");
			bp++;
			continue;

		case '`':	/* no back quotes in output */
			p = putbuf(p, "\\140");
			bp++;
			continue;

		case '\\':
		case '^':	/* anything following is OK */
			*p++ = *bp++;
		}
		*p++ = *bp++;
	}
	*p++ = ':';	/* we skipped the last : with the : lookahead hack */
	(void) write(STDOUT, buf, p-buf);
}

int
cancelled(char *cap)
{
	int i;

	for (i = 0; i < ncap; i++) {
		if (cap[0] == delcap[i][0] && cap[1] == delcap[i][1])
			return (YES);
	}
	/* delete a second occurrance of the same capability */
	delcap[ncap][0] = cap[0];
	delcap[ncap][1] = cap[1];
	ncap++;
	return (cap[2] == '@');
}

char *
putbuf(ptr, str)
char	*ptr;
char	*str;
{
	char buf[20];

	while (*str) {
		switch (*str) {
		case '\033':
			ptr = putbuf(ptr, "\\E");
			str++;
			break;
		default:
			if (*str <= ' ') {
				(void) sprintf(buf, "\\%03o", *str);
				ptr = putbuf(ptr, buf);
				str++;
			} else
				*ptr++ = *str++;
		}
	}
	return (ptr);
}

int
baudrate(char *p)
{
	char buf[8];
	int i = 0;

	while (i < 7 && (isalnum(*p) || *p == '.'))
		buf[i++] = *p++;
	buf[i] = '\0';
	for (i = 0; speeds[i].string; i++)
		if (sequal(speeds[i].string, buf))
			return (speeds[i].speed);
	return (-1);
}

char *
mapped(type)
char	*type;
{
	extern short	ospeed;
	int	match;

#ifdef DEB
	printf("spd:%d\n", ospeed);
	prmap();
#endif
	Map = map;
	while (Map->Ident) {
		if (*(Map->Ident) == '\0' ||
		    sequal(Map->Ident, type) || isalias(Map->Ident)) {
			match = NO;
			switch (Map->Test) {
			case ANY:	/* no test specified */
			case ALL:
				match = YES;
				break;

			case GT:
				match = (ospeed > Map->Speed);
				break;

			case GE:
				match = (ospeed >= Map->Speed);
				break;

			case EQ:
				match = (ospeed == Map->Speed);
				break;

			case LE:
				match = (ospeed <= Map->Speed);
				break;

			case LT:
				match = (ospeed < Map->Speed);
				break;

			case NE:
				match = (ospeed != Map->Speed);
				break;
			}
			if (match)
				return (Map->Type);
		}
		Map++;
	}
	/* no match found; return given type */
	return (type);
}

#ifdef DEB
prmap()
{
	Map = map;
	while (Map->Ident) {
		printf("%s t:%d s:%d %s\n",
		    Map->Ident, Map->Test, Map->Speed, Map->Type);
		Map++;
	}
}
#endif

char *
nextarg(argc, argv)
int	argc;
char	*argv[];
{
	if (argc <= 0)
		fatal("Too few args: ", *argv);
	if (*(*++argv) == '-')
		fatal("Unexpected arg: ", *argv);
	return (*argv);
}

void
fatal(char *mesg, char *obj)
{
	prs(mesg);
	prs(obj);
	prc('\n');
	prs(USAGE);
	flush();
	exit(1);
}


/*
 * Stolen from /usr/src/ucb/reset.c, which this mod obsoletes.
 */
char
reset(ch, def)
	char ch;
	int def;
{
	if (ch == 0 || (ch&0377) == 0377)
		return (def);
	return (ch);
}
